#include <string.h>
#include <unistd.h>
#include "opts.h"

/// @brief 获取opts 解析字符串
/// @param opts
/// @param buf 长度为opts->opt_arr_len * 2 + 1的空间
static inline void __opts_str_get(opts_t *opts, char *buf)
{
    memset(buf, 0, opts->opt_arr_len * 2 + 1);
    size_t i = 0;
    for (i = 0; i < opts->opt_arr_len; ++i)
    {
        strncat(buf, &(opts->opt_arr[i].opt), 1);
        if (opts->opt_arr[i].state & OPT_STATE_NEEDARG)
        {
            strcat(buf, ":");
        }
    }
}

static inline int opts_parse(opts_t *opts, int argc, char *argv[], char *tmpbuf)
{
    int err = 0;
    int parse_err_ret = 0;
    int opt = 0;
    size_t i = 0;
    __opts_str_get(opts, tmpbuf);
    while ((opt = getopt(argc, argv, tmpbuf)) != -1)
    {
        for (i = 0; i < opts->opt_arr_len; i++)
        {
            if (opts->opt_arr[i].opt == opt)
            {
                err = 0;
                // seted => data_deint (reset)
                if (opts->opt_arr[i].state & OPT_STATE_HASDATA && opts->opt_arr[i].data != NULL && opts->opt_arr[i].data_deinit != NULL)
                {
                    opts->opt_arr[i].state &= ~OPT_STATE_HASDATA;
                    opts->opt_arr[i].data_deinit(&(opts->opt_arr[i]));
                    opts->opt_arr[i].data = NULL;
                    // printf("%c data reset!\n", opts->opt_arr[i].opt);
                }

                if (opts->opt_arr[i].setter != NULL)
                {
                    err = opts->opt_arr[i].setter(opts, &(opts->opt_arr[i]), optarg);
                    opts->opt_arr[i].state |= OPT_STATE_HASDATA;
                    // printf("%c data set!\n", opts->opt_arr[i].opt);

                    // setter err => errtry || return
                    if (err)
                    {
                        if (opts->parse_err == NULL)
                            return err;

                        parse_err_ret = opts->parse_err(opts, &(opts->opt_arr[i]), err, optarg);
                        if (parse_err_ret == OPTS_PARSE_ERR_CONTINUE)
                            continue;
                        else if (parse_err_ret == OPTS_PARSE_ERR_RETURN)
                            return err;
                        else
                        {
                            fprintf(stderr, "The parse_err() return value %d is not between %s and %s\n", parse_err_ret, "OPTS_PARSE_ERR_CONTINUE", "OPTS_PARSE_ERR_RETURN");
                            return parse_err_ret;
                        }
                    }
                }
            }
        }
    }
    // check
    for (size_t i = 0; i < opts->opt_arr_len; i++)
    {
        if (opts->opt_arr[i].state & OPT_STATE_REQUIRED)
        {
            if (opts->opt_arr[i].state & OPT_STATE_HASDATA)
            {
                if (opts->opt_arr[i].data == NULL)
                    fprintf(stderr, "tips: opts_parse() -%c data is null\n", opts->opt_arr[i].opt);
            }
            else
            {
                fprintf(stderr, "opts_parse() required -%c\n", opts->opt_arr[i].opt);
                err = OPTS_CHECK_FAIL;
            }
        }
    }
    if (err == OPTS_CHECK_FAIL)
    {
        for (size_t i = 0; i < opts->opt_arr_len; i++)
        {
            if (opts->opt_arr[i].state & OPT_STATE_USAGE)
            {
                fprintf(stderr, "-%c to check usage!\n", opts->opt_arr[i].opt);
            }
        }
    }
    return err;
}

static inline void *opts_getter(opts_t *opts, char opt)
{
    size_t i = 0;
    for (i = 0; i < opts->opt_arr_len; i++)
    {
        if (opts->opt_arr[i].opt == opt)
        {
            return opts->opt_arr[i].data;
        }
    }
    return NULL;
}

int opts_init(opts_t *opts, opt_t *opt_arr, size_t opt_arr_len, opts_parse_err_impl parse_err)
{
    if (opts == NULL || opt_arr == NULL || opt_arr_len <= 0)
        return -1;

    opts->opt_arr = opt_arr;
    opts->opt_arr_len = opt_arr_len;
    opts->parse = opts_parse;
    opts->parse_err = parse_err;
    opts->getter = opts_getter;

    return 0;
}

void opts_deinit(opts_t *opts)
{
    if (opts == NULL)
        return;
    size_t i = 0;
    for (i = 0; i < opts->opt_arr_len; i++)
    {
        // printf("%c data_deinit start\n", opts->opt_arr[i].opt);
        if (opts->opt_arr[i].data_deinit != NULL && opts->opt_arr[i].state & OPT_STATE_HASDATA && opts->opt_arr[i].data != NULL)
        {
            opts->opt_arr[i].data_deinit(&(opts->opt_arr[i]));
            opts->opt_arr[i].state &= ~OPT_STATE_HASDATA;
        }
        // printf("%c data_deinit end\n", opts->opt_arr[i].opt);
    }
}